package hirt;

import java.util.Vector;
import java.lang.Exception.*;
import java.io.*;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Scenario is the class that contains everything pertaining to a snapshot in
 * time to describe the state of the road or highway containing all items.
 * Like an Item, it has an item list that contains every item in the scenario.
 * Note that the current implementation also includes Player's, as the Player
 * type is considered an Item. This should probably be changed in the future,
 * as many workarounds and bugs can arise from tying temporary Players to
 * a Scenario.
 *
 * The top most Item2D in Scenario is the world item, which represents the
 * visible world, containing all relevant items.
 * @author puzzud
 */
public class Scenario implements Cloneable
{
    /** An item that contatains every item that appears in the scene */
    protected Item2D world;
    /** The current time */
    protected double currentTime;
    //protected double completionTime;
    /** A list of all the lanes in the scenario */
    protected Vector<Lane> laneList;
    /** A master item list of every item that can or has appeared in the scenario */
    protected Vector<Item> itemList;
    //protected double estimatedDuration;
    /** The speed limit of the road the scenario takes place on */
    protected int speedLimit;
    /** The name of the scenario */
    protected String name;
    /** The name of the user that made this scenario */
    protected String authorName;
    /** A String description of where the scenario takes place */
    protected String location;
    /** A user written description of the scenario they had created */
    protected String description;
    /** A float description of how heavy traffic is */
    protected float trafficLevel;
    /** The number of crashed car */
    protected int crashedCarNum;
    /** The actions that must be complete before the scenario is considered complete
     * i.e. tow the victims, etc
     */
    protected Vector<Action> objectives;
    /** A boolean value that only lets the world object be assigned once */
    private boolean setWorld;
    /** The string name of the file this scenario was loaded from */
    private String originalFile;
    //protected Solution optimalSolution;
    //protected Solution performedSolution;
    //protected Vector<hirt.Action> objectiveList;

    public Scenario()
    {
        world = new Item2D();
        world.setType( "world" );
        setWorld = true;
        currentTime = 0;
        //completionTime = -1;
        laneList = new Vector<Lane>();
        itemList = new Vector<Item>();
        
        //addItem(world);
        authorName = "";
        name = "";

        //estimatedDuration = 0;
        speedLimit = 55;
        location = "";
        crashedCarNum = 0;
        trafficLevel = 0.0f;
        description = "";
    }

    // Andrew2Zack: TODO Retrofit to use clone().
    public Scenario( Scenario scenarioCopy )
    {

    }

    public void setCurrentTime( double currentTime ) throws Exception
    {
        if (currentTime >= 0 )
        {
            this.currentTime = currentTime;
        }
        else
        {
            throw new Exception("Invalid Input");
        }
    }

    public double getCurrentTime()
    {
        return( currentTime );
    }

    public void setLaneList( Vector<Lane> laneList )
    {
        this.laneList = laneList;
    }

    public Vector<Lane> getLaneList()
    {
        return( laneList );
    }

    /*public void setItemList( Vector<Item> itemList )
    {
        this.itemList = itemList;
    }*/

    public Vector<Item> getItemList()
    {
        return( itemList );
    }

    /**
     * Adds an item to the master item list and sets its ID to match it's place
     * in the list
     * @param item
     */
    public void addItem(Item item)
    {
        item.setId(itemList.size());
        itemList.add(item);
    }

    public void addItem(Item item, int index)
    {
        itemList.add(index, item);
        
    }


    public void removeItem(Item item)
    {
        if (itemList.contains(item))
        {
            /*if (item instanceof Automobile)
            {
                Player controller = getPlayerByRole((Automobile)item.getRole());
            }*/
            for (int i = item.getId()+1; i < itemList.size(); i++)
            {
                itemList.get(i).setId(i-1);
            }
            itemList.remove(item);
            item.setId(-1);
        }
    }

    /**
     * Returns the item of a certain ID from the master item list
     * @param id
     * @return
     */
    public Item getItemById( int id )
    {
        int index;
        Item item;

        for( index = 0; index < itemList.size(); index++ )
        {
            item = itemList.get( index );

            if( item.getId() == id )
            {
                return( item );
            }
        }

        return( null );
    }

    public Item removeItem(int index)
    {
        Item item = itemList.elementAt(index);
        //removeItem(item);
        return item;
    }

    public String getOriginalFile() {
        return originalFile;
    }



    /*public void setEstimatedDuration( double estimatedDuration ) throws Exception
    {
        if (estimatedDuration >= 0)
        {
            this.estimatedDuration = estimatedDuration;
        }
        else
        {
            throw new Exception("Invalid Input");
        }
    }

    public double getEstimatedDuration()
    {
        return( estimatedDuration );
    }*/

    public void setSpeedLimit( int speedLimit ) throws Exception
    {
        if (speedLimit > 0 && speedLimit % 5 == 0 && speedLimit < 100)
        {
            this.speedLimit = speedLimit;
        }
        else
        {
            throw new Exception("Invalid Input");
        }
    }

    public int getSpeedLimit()
    {
        return( speedLimit );
    }

    public void setName( String name )
    {
        this.name = name;
    }

    public String getName()
    {
        return( name );
    }

    public void setAuthorName( String authorName )
    {
        this.authorName = authorName;
    }

    public String getAuthorName()
    {
        return( authorName );
    }

    public void setLocation( String location )
    {
        this.location = location;
    }

    public String getLocation()
    {
        return( location );
    }

    /**
     * setDescription uses the scenario's details to create a description.
     * @author tpruitt
     */
    public void setDescription()
    {
        // TODO test code
        // TODO check the traffic level numbers

        String trafficLevelFiller;

        if (trafficLevel > 0.75 && trafficLevel <= 1.0) {
            trafficLevelFiller = "super heavy";
        }
        else if (trafficLevel > 0.50 && trafficLevel <= 0.75) {
            trafficLevelFiller = "heavy";
        }
        else if (trafficLevel > 0.25 && trafficLevel <= 0.50) {
            trafficLevelFiller = "moderate";
        }
        else if (trafficLevel > 0.0 && trafficLevel <= 0.25) {
            trafficLevelFiller = "light";
        }
        else if (trafficLevel == 0.0) {
            trafficLevelFiller = "no";
        }
        else {
            System.out.println("Traffic Level cannot be determined");
            trafficLevelFiller = "";
        }

        this.description = crashedCarNum + " vehicle crash on " + location
                + " with " + trafficLevelFiller + " traffic and a " + speedLimit
                + " MPH speed limit.";
    }

    public void setDescription( String description )
    {
        this.description = description;
    }

    public String getDescription()
    {
        return( description );
    }

    public float getTrafficLevel()
    {
        return( trafficLevel );
    }

    public void setTrafficLevel( float trafficLevel )
    {
        this.trafficLevel = trafficLevel;
    }

    protected void setNumberOfLanes( int numberOfLanes )
    {
        // TODO: Perhaps this should nicely preserve the objects in what lanes are being removed.
        //initializeLanes( numberOfLanes );
    }

    public int getNumberOfLanes()
    {
        // NOTE: Perhaps only calculate this once for runtime efficiency.
        return( laneList.size() );
    }

    /**
     * getNumberOfAutomobiles
     * @param roleId role id of which type of car to count.
     * @return the number of current automobiles with this role id.
     *
     */
    public int getNumberOfAutomobiles( int roleId )
    {
        int index;
        int count;

        for( index = 0, count = 0; index < itemList.size(); index++ )
        {
            Item tempItem = itemList.get( index );

            if( tempItem instanceof Automobile )
            {
                Automobile tempAutomobile = ( Automobile )tempItem;

                if( tempAutomobile.getRoleId() == roleId )
                {
                    count++;
                }
            }
        }

        return( count );
    }

    /*
     * Check the parameterized item with all the other items (no lanes or non item2D) in the world.
     */
    /*
    public boolean checkItemCollision( Item2D checkItem )
    {
        int index;
        
        // TODO: Wasteful... really just check visible items (not all items in scenario, like non vis cones).
        for( index = 0; index < itemList.size(); index++ )
        {
            Item tempItem = itemList.get( index );

            if( tempItem instanceof Item2D )
            {
                Item2D tempItem2D = ( Item2D )tempItem;

                //tempItem2D.
            }
        }

        return( false );
    }
     * 
     */

    public Vector<Action> getObjectives() {
        return objectives;
    }

    public void setObjectives(Vector<Action> objectives) {
        this.objectives = objectives;
    }

    //TODO finish the deep copy
   /*  @Override
    public Object clone()
    {
        // Deep copy.
        Scenario copiedScenario = new Scenario();

        try
        {
            copiedScenario.setAuthorName(authorName);
            copiedScenario.setCurrentTime(currentTime);
            copiedScenario.setDescription(description);
            copiedScenario.setEstimatedDuration(estimatedDuration);
            copiedScenario.setItemList(itemList.clone());
            copiedScenario.setLaneList(laneList.clone());
            copiedScenario.setLocation(location);
            copiedScenario.setName(name);
            copiedScenario.setObjectives(objectives.clone());
            copiedScenario.setSpeedLimit(speedLimit);
            copiedScenario.setTrafficLevel(trafficLevel);
        }
        catch (Exception ex)
        {
            return null;
        }
        // Andrew2Zack: TODO We need to deep copy all vectors and the objects therein.

        return( copiedScenario );
    }

    private void setLaneList(Object clone) {
        Vector<Lane> newList = new Vector<Lane>();
        for (int i = 0; i < laneList.size(); i++)
        {
            newList.add(laneList.get(i));
        }
        clone = newList;
    }

    private void setItemList(Object clone) {
        Vector<Item> newList = new Vector<Item>();
        for (int i = 0; i < itemList.size(); i++)
        {
            newList.add(itemList.get(i));
        }
        clone = newList;
    }

    private void setObjectives(Object clone) {
        Vector<Action> newList = new Vector<Action>();
        for (int i = 0; i < objectives.size(); i++)
        {
            newList.add(objectives.get(i));
        }
        clone = newList;
    }
*/

    /**
     * Creates a set of cars in a preprogrammed posistion and angles
     */
    public void initializeCars()
    {
        // *******************************************************************
        // NOTE - This code block initializes this data - TEMPORARY!
        // Initialize all the current car objects.
        initializeCar( Role.VICTIM, 140, 150, 0.0f );
        initializeCar( Role.VICTIM, 140, 220, 0.0f );
        initializeCar( Role.POLICE, 265, 250, 0.0f );
        initializeCar( Role.EMS, 200, 350, 0.0f );
        initializeCar( Role.FIRE, 200, 450, 30.0f );
        initializeCar( Role.TOW, 300, 550, 0.0f );
        initializeCar( Role.TOW, 140, 50, 0.0f );
        initializeCar( Role.IDOT, 200, 600, 0.0f );
        // *******************************************************************
    }

    /**
     * Sets up the passed cars with the passed values
     * @param roleId
     * @param xPosition
     * @param yPosition
     * @param angle
     * @return
     */
    public Automobile initializeCar( int roleId, int xPosition, int yPosition, float angle )
    {
        Automobile newAutomobile;

        newAutomobile = new Automobile( roleId );
        newAutomobile.setPosition( xPosition, yPosition );
        newAutomobile.setAngle( angle );

        // Determine Automobile instance (based on current number of type of cars).
        int index;
        int count = 0;
        for( index = 0; index < itemList.size(); index++ )
        {
            Item item = itemList.get( index );

            if( item instanceof Automobile )
            {
                Automobile tempAutomobile = ( Automobile )item;

                if( tempAutomobile.getRoleId() == roleId )
                {
                    count++;
                }
            }
        }
        newAutomobile.setInstance( count + 1 );

        addItem( newAutomobile );
        world.putItem( newAutomobile );

        return( newAutomobile );
    }

    /*
    public void removeAllLanes()
    {
        int index;

        for( index = 0; index < getNumberOfLanes(); index++ )
        {
            for( index i)

            laneList.remove( index );
        }

        laneList.clear();
    }
    */

    /*public double getCompletionTime()
    {
        return( completionTime );
    }

    public void setCompletionTime( double completionTime )
    {
        this.completionTime = completionTime;
    }*/

    public Item2D getWorld()
    {
        return( world );
    }

    public void setWorld( Item2D world )
    {
        if (setWorld)
        {
            this.world = world;
            setWorld = false;
        }
    }

    

    /**
     * Quit a poor collision detection algorithm for the entire world.
     * Assume we are not checking against lanes.
     * @param itemToCheck
     * @return
     */
    public boolean detectCollision( Item2D itemToCheck )
    {
        if( world == null )
        {
            return( false );
        }

        int index;
        LinkedList<Item> worldItemList = world.getItemList();
        int size = worldItemList.size();
        for( index = 0; index < size; index++ )
        {
            Item tempItem = worldItemList.get( index );

            if( itemToCheck == tempItem )
            {
                // We are comparing the same item, skip this.
                continue;
            }
            else
            if( ( itemToCheck instanceof Lane ) )
            {
                // NOTE: Right now, let's now worry about lanes colliding with anything else.
                return( false );
            }
            else
            if( ( itemToCheck instanceof Item2D ) )
            {
                if( !( tempItem instanceof Lane ) )
                {
                    if( itemToCheck.detectCollision( ( Item2D )tempItem ) == true )
                    {
                        //System.out.println( "COLLISION: " + itemToCheck.getId() + ":" + tempItem.getId() );

                        return( true );
                    }
                }
            }
        }

        return( false );
    }

    /**
     * Goes through the master item list and removes all items the have been removed in game
     */
    private void pruneItemList()
    {
        Item item;
        for (int i = 0; i < itemList.size(); i++)
        {
            item = itemList.get(i);
            if (item.container==null && !item.type.equals("world") && !(item instanceof Player))
            {
                //itemList.remove(item);
                removeItem(item);
                i--;
            }
        }
    }

    /**
     * Saves the scenario to under passed name
     * Scenario Name will be set to the name and
     * the file name will be set to the name
     * @param name
     * @return whther or not the save was successful
     */
    public boolean writeToFile(String fileName)
    {
        pruneItemList();
        FileWriter fstream = null;
        BufferedWriter out = null;
        try
        {
            fstream = new FileWriter(fileName);
            out = new BufferedWriter(fstream);
            out.write(name + "\n");
                       //  System.out.print(name + "\n");
            out.write(authorName + "\n");
                         //  System.out.print(authorName + "\n");
            out.write(location + "\n");
                        //  System.out.print(location + "\n");
            out.write(description + "\n");
                        // System.out.print(description + "\n");
            out.write(speedLimit + "\n");
                          // System.out.print(speedLimit + "\n");
            //out.write(laneList.size() + "\n");
                           //System.out.print(laneList.size() + "\n");
            //int size = itemList.size();
            Vector<Item> removedPlayers = new Vector<Item>();
            for (int i = 0; i < itemList.size(); i++)
            {
                if (itemList.get(i) instanceof Player)
                {
                    removeItem(itemList.get(i));
                    removedPlayers.add(itemList.get(i));
                }
            }
            //for (int i = 0; i < removedPlayers.size(); i++)
                //removeItem(removedPlayers.get(i));
            out.write(itemList.size() + "\n");
                          //System.out.print(itemList.size() + "\n");
            Item item;
            String save;
            for (int i = 0; i<itemList.size();i++)
            {
                save = "";
                item = itemList.get(i);
                if (!(item instanceof Player))
                {
                    save = CreationAction.getItemTypeInitial(item);

                    if (!save.equals(""))
                    {
                        save += item.toSaveString();
                        out.write(save + "\n");
                        //System.out.print(save + "\n");
                    }
                }
            }

            for (int i = 0; i < removedPlayers.size(); i++) {
                addItem(removedPlayers.get(i));
                
            }


        } catch (IOException ex) {
            return false;
        } finally {
            try {
                out.close();
                fstream.close();
            } catch (IOException ex) {
                Logger.getLogger(Scenario.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        return true;
    }

    /**
     * Opens a scenario save file and reads sets this scenarios name
     * based off of the name from the file
     * @param fileName
     * @return whther or not the load was successful
     */
    public boolean basicLoadFromFile(String fileName)
    {
        if (fileName == null)
            return false;
        
        try {
            originalFile = fileName;
            // Open the file that is the first
            // command line parameter
            FileInputStream fstream = new FileInputStream(fileName);
            // Get the object of DataInputStream
            DataInputStream in = new DataInputStream(fstream);
                BufferedReader br = new BufferedReader(new InputStreamReader(in));

            name = br.readLine();

        } catch (IOException ex) {
            return false;
        }

        return true;
    }

    /**
     * Opens the passed scenario file and builds this scenario around the data in that file
     * @param fileName
     * @return
     */
    public boolean loadFromFile(String fileName)
    {
        try {
            originalFile = fileName;
            // Open the file that is the first
            // command line parameter
            FileInputStream fstream = new FileInputStream(fileName);
            // Get the object of DataInputStream
            DataInputStream in = new DataInputStream(fstream);
                BufferedReader br = new BufferedReader(new InputStreamReader(in));
            String strLine;
            name = br.readLine();
            authorName = br.readLine();
            location = br.readLine();
            description = br.readLine();
            strLine = br.readLine();
            speedLimit = Integer.parseInt(strLine);
            //strLine = br.readLine();
            //int lanes = Integer.parseInt(strLine);
            //setNumberOfLanes(lanes);
            strLine = br.readLine();
            int length = Integer.parseInt(strLine);
            itemList = new Vector<Item>();
            Item item;
            for (int i = 0; i < length; i++)
            {
                //System.out.println("Loop: " + i);
                strLine = br.readLine();
                item = null;
                //System.out.println("Type: " + strLine.substring(0, 1));
                if (strLine.startsWith("A"))
                    item = new Automobile(strLine.substring(1), itemList);
                else if (strLine.startsWith("D"))
                {
                    item = new Item2D(strLine.substring(1), itemList);
                    if (item.type.equals("world"))
                        setWorld((Item2D) item);
                }
                else if (strLine.startsWith("I"))
                    item = new Item(strLine.substring(1), itemList);
                else if (strLine.startsWith("L"))
                {
                    item = new Lane(strLine.substring(1), itemList);
                    laneList.add((Lane) item);
                }

                if (item != null)
                    itemList.add(item);

            }



        } catch (IOException ex) {
            return false;
        }

        return true;
    }

    /**
     * Clears all the data from this scenario
     */
    public void clearScenario()
    {
        name = "";
        authorName = "";
        location = "";
        description = "";
        speedLimit = -1;
        laneList.clear();
        itemList.clear();
        setWorld = true;
        originalFile = "";
        world.itemList.clear();

    }
}
